package com.gsbelarus.gedemin.lib.sync.protocol;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;

import com.gsbelarus.gedemin.lib.R;
import com.gsbelarus.gedemin.lib.sync.protocol.entity.SyncProperty;
import com.gsbelarus.gedemin.lib.sync.protocol.entity.SyncServiceStatus;
import com.gsbelarus.gedemin.lib.sync.protocol.entity.SyncServiceTask;
import com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executors;

abstract public class SyncServiceModel<T> extends Service implements SyncProtocolModel {

    protected Context context;
    protected SyncNotificationAdapter syncNotificationAdapter;
    protected SyncProtocol protocol;

    private List<SyncServiceTask<T>> seriesTasks;
    private SyncServiceTask<T> currentTask;

    public static boolean isShowNotificationError = true;
    private boolean isCreatingDemoDB = false;
    private boolean onStartSubTask;

    public static Intent getIntentWithTask(Intent intent, SyncServiceTask.TypeOfTask typeOfTask) {
        return intent.putExtra(SyncServiceTask.TypeOfTask.class.getSimpleName(), new SyncServiceTask(typeOfTask));
    }

    public static Intent getIntentWithTask(Intent intent, SyncServiceTask.TypeOfTask typeOfTask, boolean isNotifyAboutSyncServiceStatus) {
        return intent.putExtra(SyncServiceTask.TypeOfTask.class.getSimpleName(), new SyncServiceTask(typeOfTask, isNotifyAboutSyncServiceStatus));
    }

    @SuppressWarnings("unchecked")
    public static Intent getIntentWithTask(Intent intent, SyncServiceTask.TypeOfTask typeOfTask, Object subTask) {
        return intent.putExtra(SyncServiceTask.TypeOfTask.class.getSimpleName(), new SyncServiceTask(typeOfTask).setSubTask(subTask));
    }

    @SuppressWarnings("unchecked")
    public static Intent getIntentWithTask(Intent intent, SyncServiceTask.TypeOfTask typeOfTask, Object subTask, boolean isNotifyAboutSyncServiceStatus) {
        return intent.putExtra(SyncServiceTask.TypeOfTask.class.getSimpleName(), new SyncServiceTask(typeOfTask, subTask, isNotifyAboutSyncServiceStatus));
    }

    public static String getBroadcastMessageAction(Context context) {
        return context.getPackageName() + "(broadcast_messages_about_the_status_sync)";
    }

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        syncNotificationAdapter = getSyncNotificationAdapter();
        protocol = new SyncProtocol(context, this);
        seriesTasks = new ArrayList<>();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            @SuppressWarnings("unchecked")
            SyncServiceTask<T> syncServiceTask = (SyncServiceTask<T>) intent.getSerializableExtra(SyncServiceTask.TypeOfTask.class.getSimpleName());
            if (syncServiceTask == null)
                syncServiceTask = new SyncServiceTask<>(SyncServiceTask.TypeOfTask.SINGLE_SYNC);

            if (syncServiceTask.getTypeOfTask() == SyncServiceTask.TypeOfTask.STOP_SYNC) {
                seriesTasks.clear();
                protocol.setStopSync(true);

            } else {
                startTask(syncServiceTask);
            }
        }
        return START_STICKY;
    }

    /**
     * Запустить синхронизацию с нужным Task-ом
     *
     * @param syncServiceTask нужный Task, при необходимости можно поместить внуть subTask
     *                        (использовать метод  {@link SyncServiceTask#setSubTask(Object)})
     */
    protected void startTask(SyncServiceTask<T> syncServiceTask) {
        if (!protocol.isWork() && !isCreatingDemoDB && !onStartSubTask) {
            boolean isWorkMode = isWorkMode();
            currentTask = syncServiceTask;

            onStartSubTask = true;
            if (syncServiceTask.getSubTask() != null && !onStartSubTask(syncServiceTask)) {
                onStartSubTask = false;
                startFirstSeriesTask();
                return;
            }
            onStartSubTask = false;

            switch (syncServiceTask.getTypeOfTask()) {
                case SINGLE_SYNC:
                    if (checkDemoDB(isWorkMode)) break;
                    startSync();
                    break;
                case SERIES_SYNC:
                    if (checkDemoDB(isWorkMode)) break;
                    startSync();
                    break;
                case NO_SYNC:
                    notifyAboutSyncServiceStatus(new SyncServiceStatus<T>()
                            .setTypeOfStatus(SyncServiceStatus.TypeOfStatus.FINISH_SYNC)
                            .setStatus(SyncProtocol.SyncStatus.SUCCESSFUL.setMessage(""))
                            .setTask(getCurrentTask()));
                    stopSelf();
                    break;
            }
        } else {
            if (syncServiceTask.getTypeOfTask() == SyncServiceTask.TypeOfTask.SERIES_SYNC)
                seriesTasks.add(syncServiceTask);
            else
                notifyAboutSyncServiceStatus(new SyncServiceStatus<T>()
                        .setTypeOfStatus(SyncServiceStatus.TypeOfStatus.START_SYNC)
                        .setStatus(SyncProtocol.SyncStatus.SUCCESSFUL.setMessage(""))
                        .setTask(getCurrentTask()));
        }
    }

    /**
     * Вызывается если пришел кастомный Task
     *
     * @param syncServiceTask содержит кастомный Task
     * @return если true - выполнение синхронизации продолжится с полученым Task-ом
     * если false - выполнение синхронизации с полученным Task-ом прекратится
     */
    protected boolean onStartSubTask(SyncServiceTask<T> syncServiceTask) {
        return true;
    }

    private boolean checkDemoDB(boolean isWorkMode) {
        if (isWorkMode) return false;

        onPreExecute();
        isCreatingDemoDB = true;
        final Handler handler = new Handler();
        Executors.newFixedThreadPool(1).execute(new Runnable() {
            @Override
            public void run() {
                onCreateDemoDB();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        isCreatingDemoDB = false;
                        onPostExecute(SyncProtocol.SyncStatus.SUCCESSFUL.setMessage(getString(R.string.created_demo_db_text)));
                    }
                });
            }
        });
        return true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        protocol.setStopSync(true);
    }

    /**
     * Провести синхронизацию.
     *
     * @return true - синхронизация запущена, false - синхронизация уже работает
     */
    protected boolean startSync() {
        return protocol.doSync(getSyncProperty());
    }

    /**
     * Выполняется во время подготовки к созданию нового потока загрузки, имеет доступ к UI потоку
     */
    @Override
    public void onPreExecute() {
        startForeground(syncNotificationAdapter.getForegroundId(),
                syncNotificationAdapter.buildStartNotification(isWorkMode()));

        notifyAboutSyncServiceStatus(new SyncServiceStatus<T>()
                .setTypeOfStatus(SyncServiceStatus.TypeOfStatus.START_SYNC)
                .setStatus(SyncProtocol.SyncStatus.SUCCESSFUL.setMessage(""))
                .setTask(getCurrentTask()));
    }

    /**
     * Обновление состояния загрузки
     *
     * @param percent      процент загрузки
     * @param currentBlock загружаемый блок
     * @param totalBlocks  общее количество блоков
     */
    @Override
    public void onProgressUpdate(int percent, int currentBlock, int totalBlocks) {
        if (!protocol.isStopSync()) {
            if (percent == 101)
                syncNotificationAdapter.showUpdatingNotification();
            else
                syncNotificationAdapter.showDownloadingNotification(percent, currentBlock, totalBlocks);
        }
    }

    /**
     * Открыть транзакцию, выполняется в потоке загрузки
     */
    @Override
    abstract public void onBeginTransaction();

    /**
     * Вызывается после получения ответа, в потоке загрузки.
     * Для вывода ошибки необходимо сгененировать исключение CustomSyncException("локализованное название ошибки")
     *
     * @param headerMap массив праметров из заголовка.
     * @throws com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException прерывает загрузку данных, отображает диолог об ошибке
     */
    @Override
    abstract public void onHeaderLoaded(HashMap<String, String> headerMap) throws CustomSyncException;

    /**
     * Вызывается после получения ответа, в потоке загрузки.
     * Для вывода ошибки необходимо сгененировать исключение CustomSyncException("локализованное название ошибки")
     *
     * @param lines тело блока в виде строк. Следует распарсить и вставить в БД.
     * @throws com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException прерывает загрузку данных, отображает диолог об ошибке
     */
    @Override
    abstract public void onBlockLoaded(List<String> lines) throws CustomSyncException;

    /**
     * Закрыть транзакцию успешно, выполняется в потоке загрузки
     */
    @Override
    abstract public void onEndTransaction();

    /**
     * Закрыть транзакцию с откатом изменений, выполняется в потоке загрузки
     */
    @Override
    abstract public void onCancelTransaction();

    /**
     * Выполняется после загрузки, имеет доступ к UI потоку
     * внутри следует обрабатывать ошибки
     *
     * @param status статус с каким завершилась синхронизация
     *               (NO_INTERNET_CONNECTION, ADDRESS_FAILED, TIMEOUT, NOT_REQUIRED,
     *               ELSE_FAILED, SUCCESSFUL, STOP_SYNC, CUSTOM_SYNC_FAILED)
     */
    @Override
    public void onPostExecute(SyncProtocol.SyncStatus status) {
        if (status != SyncProtocol.SyncStatus.SUCCESSFUL)
            seriesTasks.clear();
        if (startFirstSeriesTask())
            return;

        bindStatusMessage(status);

        switch (status) {
            case SUCCESSFUL:
                break;
            case NOT_REQUIRED:
                break;
            case STOP_SYNC:
                break;
            default:
                /** выводим ошибку в строку уведомлений*/
                if (isShowNotificationError) {
                    if (!currentTask.isBackgroundTask())
                        syncNotificationAdapter.showErrorNotification(status.getMessage());

                    else if (status == SyncProtocol.SyncStatus.CUSTOM_SYNC_FAILED)
                        syncNotificationAdapter.showErrorNotification(status.getMessage());
                }
        }

        SyncServiceStatus<T> syncServiceStatus = new SyncServiceStatus<T>()
                .setTypeOfStatus(SyncServiceStatus.TypeOfStatus.FINISH_SYNC)
                .setStatus(status)
                .setTask(getCurrentTask());

        /** уведомляем активности об окончании синхронизации*/
        if (!currentTask.isBackgroundTask())
            notifyAboutSyncServiceStatus(syncServiceStatus);

        else if (status == SyncProtocol.SyncStatus.SUCCESSFUL || status == SyncProtocol.SyncStatus.CUSTOM_SYNC_FAILED)
            notifyAboutSyncServiceStatus(syncServiceStatus);

        stopForegroundTask();
    }

    private void stopForegroundTask() {
        stopForeground(true);
        syncNotificationAdapter.clearForegroundNotification();
        stopSelf();
    }

    private void bindStatusMessage(SyncProtocol.SyncStatus status) {
        switch (status) {
            case ADDRESS_FAILED:
                status.setMessage(getString(R.string.sync_status_error_address));
                break;
            case TIMEOUT:
                status.setMessage(getString(R.string.sync_status_error_timeout));
                break;
            case NO_INTERNET_CONNECTION:
                status.setMessage(getString(R.string.sync_status_error_internet));
                break;
            case NOT_REQUIRED:
                status.setMessage("");
                break;
            case ELSE_FAILED:
                status.setMessage(getString(R.string.sync_status_error));
                break;
            case STOP_SYNC:
                status.setMessage("");
                break;
            case SUCCESSFUL:
                break;
            case CUSTOM_SYNC_FAILED:
                break;
        }
    }

    private void notifyAboutSyncServiceStatus(SyncServiceStatus<T> syncServiceStatus) {
        Intent intent = new Intent(getBroadcastMessageAction(getApplicationContext()));
        intent.putExtra(SyncServiceStatus.class.getSimpleName(), syncServiceStatus);
        sendBroadcast(intent);
    }

    private boolean startFirstSeriesTask() {
        if (!seriesTasks.isEmpty()) {
            if (isWorkMode()) {
                startTask(seriesTasks.get(0));
                if (!seriesTasks.isEmpty())
                    seriesTasks.remove(0);
                return true;
            } else {
                seriesTasks.clear();
            }
        }
        return false;
    }

    /**
     * @return в каком режиме проводить синхронизацию. Вызывается перед каждой синхронизацей.
     */
    abstract protected boolean isWorkMode();

    abstract protected SyncNotificationAdapter getSyncNotificationAdapter();

    /**
     * @return параметры для запросов, вызывается перед каждой синхронизацией
     */
    abstract protected SyncProperty getSyncProperty();

    /**
     * создание демо данных, выполняется в другом потоке
     */
    abstract protected void onCreateDemoDB();

    protected void clearSeriesTasks() {
        seriesTasks.clear();
    }

    protected int getCountSeriesTask() {
        return seriesTasks.size();
    }

    protected SyncServiceTask<T> getCurrentTask() {
        return currentTask;
    }

    protected boolean isCreatingDemoDB() {
        return isCreatingDemoDB;
    }
}
